# -*- coding:utf-8 -*-
# this code is following GPL v3
# by: wangxing(wangxing.pku@gmail.com)
# project homepage: http://code.google.com/p/py-som/
# create time: 2012-12-20
# lastupdate: 2012-12-21

from params import gettrainparamsdic

import random
import simplejson
import math
import sys


debug = True#set to False for faster speed
normalize = True

class SOMTrain:
    """
    this is an implementation of the self organizing map algorithm

    reference:
    1
    the algorithm is describe in page http://jsalatas.ictpro.gr/implementation-of-competitive-learning-networks-for-weka/
    which is the same with the weka

    2
    som_pak:http://www.cis.hut.fi/research/som_pak
    """

    def __init__(self):
        """
        model is a list of value and their position is the 2-D panel
        """
        self.datas = None
        self.model = None
        self.vlen = -1#length of vector
        self.xsize = -1
        self.ysize = -1

    
    def loaddata(self,datas):
        """
        the dataset is a list
        each item is a dic
        - labelname
        - feature vector(list of float values)
        """
        #check the lens of each data
        self.datas = datas
        if len(datas) == 0:
            raise Exception("no data")
        vlen = len(datas[0]['fv'])
        for data in datas:
            if len(data['fv']) != vlen:
                raise Exception(str(data['fv'])+ " lenght error")

    def loadarffdata(self,arffname):
        """
        load an arff data
        the last attribute is the class(label)
        """
        f = open(arffname)
        if f:
            lines = f.readlines()
            f.close()
        else:
            raise Exception("open file error")

        founddata = False
        self.datas = []
        #print lines
        for line in lines:
            if line.startswith("@data"):
                founddata = True
                #print "found"   
                continue
            if founddata == False:
                continue
            line = line.strip()
            ws = line.split(",")
            tmpdic = {}
            tmpdic['l'] = ws[-1]
            fv = []
            for i in range(len(ws)-1):
                fv.append(float(ws[i]))
            tmpdic['fv'] = fv
            #print tmpdic
            self.datas.append(tmpdic)
        #print self.datas
        self.vlen = len(self.datas[0]['fv'])

    def setparameter(self,paramsdic):
        """
        set the parameter
        """
        raise Exception("not implement")
        pass

    def trainmodel(self,xsize,ysize,maxiter):
        """
        train the model
        the parameter value
        using guassian_adapt as in som_pak/som_rout.c->gaussian_adapt
        """
        self.xsize = xsize
        self.ysize = ysize
        
        learnrate0 = 0.5
        widthp0 = math.sqrt(xsize*xsize+ysize*ysize)
        learnrate = learnrate0
        widthp = widthp0
        tao1 = float(len(self.datas)) / math.log(widthp)
        #tao2 = float(len(self.datas)) / math.log(100*learnrate)
        tao2 = 50 / math.log(100*learnrate)

        radius = xsize / 2

        # init each value
        somp = {}
        for i in range(xsize):
            somp[i] = {}
            for j in range(ysize):
                somp[i][j] = []
                #print self.vlen
                for k in range(self.vlen):
                    somp[i][j].append(random.random())
                    #print random.random()
        #print "init"
        #print somp

        # iteration
        #for i in range(len(self.datas)):
        for tmpi in range(maxiter):
            ## get the best
            print "iteration %d"%tmpi
            i = tmpi%(self.vlen)

            rt = 1 + (float)(radius-1)*(maxiter-tmpi)/maxiter
            learnrate = learnrate0*(maxiter-tmpi)/maxiter

            bestj = -1
            bestk = -1
            bestdis = 10000000
            for j in range(xsize):
                for k in range(ysize):
                    tmpdis = eucliddis(self.datas[i]['fv'],somp[j][k])
                    if tmpdis < bestdis:
                        bestdis = tmpdis
                        bestj = j
                        bestk = k
            print "i:%d,j:%d,dis:%f"%(bestj,bestk,bestdis)
            self.curbestj = bestj
            self.curbestk = bestk
            self.curfvdata = self.datas[i]['fv']
            self.curlearnrate = learnrate
            self.curradius = rt

            ## update the model according to the data
            ## w(n+1) = w(n)+learnrate*h*(x(n)-w(n))
            for j in range(xsize):
                for k in range(ysize):
                    hvalue = h(bestj,bestk,j,k,rt) #get the h value according to current radius
                    somp[j][k] = self.updatevector(somp[j][k],somp[bestj][bestk],hvalue,learnrate)
                    #re normalize?
                    if normalize:
                        somp[j][k] = normalizelist(somp[j][k])

            ## decrease learnrate and widthp
            #widthp = widthp0 * math.exp(-1*float(tmpi)/tao1)
            #learnrate = learnrate0 * math.exp(-1*float(tmpi)/tao2)

            ##for debug output
            if debug:
                # output the graph for label
                sommodel = {}
                for ti in range(xsize):
                    sommodel[ti] = {}
                    for tj in range(ysize):
                        sommodel[ti][tj] = {}
                        sommodel[ti][tj]['v'] = somp[ti][tj]
                        sommodel[ti][tj]['l'] = self.getlabel(somp[ti][tj])
                self.model = sommodel

                #self.outputgraph("midresult%d.html"%tmpi,tmpi)
                # output the graph for feature
                #self.outputfeaturegraph("midresultfv%d.html"%tmpi)
                self.outputmidresult(tmpi)

        #end of iteration

        #get the label of each unit
        sommodel = {}
        for i in range(xsize):
            sommodel[i] = {}
            for j in range(ysize):
                sommodel[i][j] = {}
                sommodel[i][j]['v'] = somp[i][j]
                sommodel[i][j]['l'] = self.getlabel(somp[i][j])

        self.model = sommodel

    def updatevector(self,wlist,xlist,hvalue,rate):
        """
        this is the same with som_pak/lvq_pak/adapter_vector
        """
        if len(wlist) != len(xlist):
            raise Exception("two length does not match")
        #rate *= math.exp(-1*hvalue*hvalue/2/self.curradius/self.curradius)
        rate *= hvalue
        deltalist = []
        for i in range(len(xlist)):
            deltalist.append(xlist[i] - wlist[i])
        newlist  = []
        for i in range(len(wlist)):
            newlist.append(wlist[i] + rate*deltalist[i])
        return newlist

    def savemodel(self,fname):
        """
        save the model to file
        """
        f = open(fname,"w")
        print>>f,simplejson.dumps(self.model)
        f.close()
    
    def outputmidresult(self,index):
        if self.model == None or self.datas == None:
            raise Exception("model is none")
        outputf = open("./data/midresult"+str(index)+".html","w")
        print>>outputf,"""<html>
        <head>
        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language='javascript' type='text/javascript'>
var theurl = "";
function load(){
    var curid = document.getElementById("pid").getAttribute("value");
    curid = parseInt(curid) + 1;
    theurl = "./midresult"+curid+".html";
    
    for(var i=2;i>=0;i--) 
    { 
        window.setTimeout('doUpdate(' + i + ')', (2-i) * 1000); 
    }
}

function doUpdate(num){
    document.getElementById('ShowDiv').innerHTML = num;
    if(num == 0){
        window.location = theurl;
    }
}

window.onload=function(){
    //alert("onload");
    load();
}

        </script>
        </head>
        <body>
        <div id="ShowDiv">时间</div> 
        <!--<script language="javascript">load();</script>-->
        """

        print>>outputf,"<table>"
        for i in range(self.xsize):
            tmpstr = "<tr>"
            for j in range(self.ysize):
                #get the best label for this model
                if i == self.curbestj and j == self.curbestk:
                    tmpstr += "<td style='color:red;'>%s</td>"%(self.model[i][j]['l'])
                else:
                    tmpstr += "<td>%s</td>"%(self.model[i][j]['l'])
            tmpstr += "</tr>"
            print>>outputf,tmpstr
        print>>outputf,"</table><input type='hidden' id='pid' value='%d'/>"%index

        print>>outputf,"<table>"
        for i in range(self.xsize):
            tmpstr = "<tr>"
            for j in range(self.ysize):
                #only two after the pointer
                if i == self.curbestj and j == self.curbestk:
                    tmpstr += "<td style='color:red;'>"
                else:
                    tmpstr += "<td>"
                
                tmpstr += self.model[i][j]['l']+":"
                
                for v in self.model[i][j]['v']:
                    tmpstr += "%.2f,"%v
                
                #calculate distance
                tmpdis = eucliddis(self.curfvdata,self.model[i][j]['v'])
                tmpstr += ":%f"%tmpdis

                tmpstr += "</td>"
            tmpstr += "</tr>"
            print>>outputf,tmpstr
        print>>outputf,"</table>"

        print>>outputf,"<p>cur vector:%s</p>"%str(self.curfvdata)
        print>>outputf,"<p>cur learn rate: %f</p>"%self.curlearnrate

        #end part
        print>>outputf,"</body></html"
        outputf.close()


    def outputgraph(self,fname,index):
        """
        check exist of the model and the data
        #add the js code to change automatic
        """
        if self.model == None or self.datas == None:
            raise Exception("model is none")
        outputf = open(fname,"w")
        print>>outputf,"""<html>
        <head>
        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language='javascript' type='text/javascript'>
var theurl = "";
function load(){
    var curid = document.getElementById("pid").getAttribute("value");
    curid = parseInt(curid) + 1;
    theurl = "./midresult"+curid+".html";
    
    for(var i=2;i>=0;i--) 
    { 
        window.setTimeout('doUpdate(' + i + ')', (2-i) * 1000); 
    }
}

function doUpdate(num){
    document.getElementById('ShowDiv').innerHTML = num;
    if(num == 0){
        window.location = theurl;
    }
}

window.onload=function(){
    //alert("onload");
    load();
}

        </script>
        </head>
        <body>
        <div id="ShowDiv">时间</div> 
        <!--<script language="javascript">load();</script>-->
        """
        print>>outputf,"<table>"
        for i in range(self.xsize):
            tmpstr = "<tr>"
            for j in range(self.ysize):
                #get the best label for this model
                tmpstr += "<td>%s</td>"%(self.model[i][j]['l'])
            tmpstr += "</tr>"
            print>>outputf,tmpstr
        print>>outputf,"</table><input type='hidden' id='pid' value='%d'/></body></html>"%index
        outputf.close()

    def outputfeaturegraph(self,fname):
        if self.model == None or self.datas == None:
            raise Exception("no model")
        outputf = open(fname,"w")
        
        print>>outputf,"<table>"
        for i in range(self.xsize):
            tmpstr = "<tr>"
            for j in range(self.ysize):
                #only two after the pointer
                tmpstr += "<td>"
                for v in self.model[i][j]['v']:
                    tmpstr += "%.2f,"%v
                tmpstr += "</td>"
            tmpstr += "</tr>"
            print>>outputf,tmpstr
        print>>outputf,"</table>"
        outputf.close()

    def getlabel(self,v):
        """
        """
        if self.datas == None:
            raise Exception("no data to get label")
        bestlabel = ""
        bestd = 1000000

        for data in self.datas:
            tmpd = eucliddis(data['fv'],v)
            if tmpd < bestd:
                bestd = tmpd
                try:
                    bestlabel = data['l']
                except Exception,e:
                    print data
                    raise Exception("no label")

        return bestlabel

def normalizelist(l):
    t = 0
    for v in l:
        t += v
    tmplist = []
    for v in l:
        tmplist.append(float(v)/t)
    return tmplist

def h(ci,cj,oi,oj,widthp):
    """
    topological neighbor's definition
    """
    tmpd = (ci-oi)*(ci-oi)+(cj-oj)*(cj-oj)
    return math.exp(-1*tmpd/2/widthp/widthp)

def cosinedis(v1,v2):
    """
    cosine distance
    """
    pass


def eucliddis(v1,v2):
    """
    using euclid distance
    """
    if len(v1) != len(v2):
        print v1
        print v2
        raise Exception("two length does not match")
    squarev = 0
    for i in range(len(v1)):
        squarev += (v1[i]-v2[i])*(v1[i]-v2[i])
    return math.sqrt(squarev)

if __name__ == "__main__":
    """
    python somtrain.py -arff=test.arff -xsize=10 -ysize=10
    """
    paramsdic = gettrainparamsdic(sys.argv)
        
    somt = SOMTrain()
    #som.loaddata(datas)
    somt.loadarffdata(paramsdic['arff'])
    somt.trainmodel(paramsdic['xsize'],paramsdic['ysize'],paramsdic['maxiter'])

    #somt.outputgraph("graphfile.html",0)
    if paramsdic.has_key("output"):
        somt.savemodel(paramsdic['output'])
